package xuan.lib;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * Operation from edit log, 
 * Each operation has its type, the node in merged document, the node in   
 * source document and possibly the parent node in source document.
 * An operation may be depends on some others and conditions some others.
 *    
 * @author xuan
 */

public class Operation {
	private TypeOp type;
	private Path mergedNodePath=null;
	private Path sourceNodePath=null;
	private Path targetNodePath=null;
	private Node mergedNode=null;
	private Node sourceNode=null;
	private Node targetNode=null;
	private Node tempNode=null;
	/**
	 * position of merged node in children list
	 */
	private int pos;
	/**
	 * dependent operations
	 */
	private List<Operation> deps =null;
	/**
	 * previous operations
	 */
	private List<Operation> prevs=null;
	/**
	 * number of non-executed previous operations
	 */
	private int nbPrevs=0;
	/**
	 * status of operation : enabled, executed, previewed
	 */
	private boolean enabled=true;
	private boolean executed=false;
	private boolean previewed = false;
	
	private Document source;
	private Document merged;
	
	
	/**
	 * Constructor
	 * @param op an operation
	 */
	public Operation(Operation op) {
		type = op.getType();
		mergedNodePath = op.getMergedNodePath();
		sourceNodePath = op.getSourceNodePath();
		targetNodePath = op.getTargetNodePath();
		mergedNode = op.getMergedNode();
		sourceNode = op.getSourceNode();
		targetNode = op.getTargetNode();
		pos = op.getPos();
		source = op.source;
		merged = op.merged;
		prevs = op.getPrevs(false);
		deps = op.getDeps(false);
	}
	
	/**
	 * Constructor
	 * @param e an element read from edit log
	 * @param source source document
	 * @param merged cible document
	 */
	public Operation(Element e, Document source, Document merged){
		this.source = source;
		this.merged = merged;
		
		type = TypeOp.valueOf(e.getTagName().toUpperCase());
		
		if(e.getAttribute("path")!=""){
			mergedNodePath = new Path(e.getAttribute("path"));
		}
		
		if(e.getAttribute("src")!=""){
			sourceNodePath = new Path(e.getAttribute("src"));
		}
		
		if(e.getAttribute("plc")!=""){
			targetNodePath = new Path(e.getAttribute("plc"));			
		}
		
		switch(type){
		case INSERT:
			mergedNode = mergedNodePath.getNode(merged.getDocumentElement());
			if (targetNodePath!=null) targetNode = targetNodePath.getNode(source.getDocumentElement());
			pos = (short)mergedNodePath.getNodeIndex();
			break;
		case DELETE:
			sourceNode = sourceNodePath.getNode(source.getDocumentElement());
			break;
		case MOVE:
			sourceNode = sourceNodePath.getNode(source.getDocumentElement());
			mergedNode = mergedNodePath.getNode(merged.getDocumentElement());
			if(targetNodePath!=null) targetNode = targetNodePath.getNode(source.getDocumentElement());
			pos = (short)mergedNodePath.getNodeIndex();
			break;
		case UPDATE:
			sourceNode = sourceNodePath.getNode(source.getDocumentElement());
			mergedNode = mergedNodePath.getNode(merged.getDocumentElement());
			break;
		default:
			sourceNode = sourceNodePath.getNode(source.getDocumentElement());
			mergedNode = mergedNodePath.getNode(merged.getDocumentElement());
			pos = (short)mergedNodePath.getNodeIndex();
			if(targetNodePath!=null) targetNode = targetNodePath.getNode(source.getDocumentElement());
		}
	}
	
	public Node getTargetNode() {
		return targetNode;
	}

	public void setTargetNode(Node targetNode) {
		this.targetNode = targetNode;
	}
	
	public Node getSourceNode() {
		return sourceNode;
	}

	public void setSourceNode(Node sourceNode) {
		this.sourceNode = sourceNode;
	}

	public Node getMergedNode() {
		return mergedNode;
	}

	public void setMergedNode(Node mergedNode) {
		this.mergedNode = mergedNode;
	}
	
	public Node getTempNode() {
		return tempNode;
	}

	public void setTempNode(Node tempNode) {
		this.tempNode = tempNode;
	}

	public TypeOp getType() {
		return type;
	}
	
	public void setType(TypeOp type) {
		this.type = type;
	}

	public boolean isEnabled() {
		return enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}

	public boolean isExecuted() {
		return executed;
	}

	public void setExecuted(boolean executed) {
		this.executed = executed;
	}

	public boolean isPreviewed() {
		return previewed;
	}

	public void setPreviewed(boolean previewed) {
		this.previewed = previewed;
	}
	
	public void setPos(int pos){
		this.pos = pos;
	}
	
	public int getPos() {
		return pos;
	}

	public Path getMergedNodePath() {
		return mergedNodePath;
	}

	public void setMergedNodePath(Path mergedNodePath) {
		this.mergedNodePath = mergedNodePath;
	}

	public Path getSourceNodePath() {
		return sourceNodePath;
	}

	public void setSourceNodePath(Path sourceNodePath) {
		this.sourceNodePath = sourceNodePath;
	}

	public Path getTargetNodePath() {
		return targetNodePath;
	}

	public void setTargetNodePath(Path targetNodePath) {
		this.targetNodePath = targetNodePath;
	}
	
	/**
	 * @return true if operation depends on any other  
	 */
	public boolean isDepended(){
		return (prevs!=null)? true:false;
	}

	
	/**
	 * Get previous operations
	 * @param recursive true if you want also get previous of previous
	 * @return previous operations
	 */
	public List<Operation> getPrevs(boolean recursive) {
		Set<Operation> prevsSet = new HashSet<Operation>();
		
		if(!recursive && prevs!=null){
			return prevs;
		}
		
		if(prevs!=null){				
			for(int i=0;i<prevs.size();i++){
				Operation op = prevs.get(i);
				prevsSet.add(op);
				prevsSet.addAll(op.getPrevs(true));
			}
		}
		
		return new LinkedList<Operation>(prevsSet);
	}

	public void setPrevs(List<Operation> prevs) {
		this.prevs = prevs;
		nbPrevs = prevs.size();
	}
	
	public void addAPreviousOperation(Operation op){
		if(prevs==null) prevs = new LinkedList<Operation>();
		prevs.add(op);
		nbPrevs++;
	}
	
	public int getNbPrevs() {
		return nbPrevs;
	}

	public void setNbPrevs(int nbPrevs) {
		this.nbPrevs = nbPrevs;
	}

	/**
	 * @return true if operation is previous of any other
	 */
	public boolean isPrevious(){
		return (deps!=null)? true:false;
	}
	
	/**
	 * Get dependents operations
	 * @param recursive true if you want also get dependents of dependents
	 * @return dependents operations
	 */
	public List<Operation> getDeps(boolean recursive) {
		Set<Operation> depsSet = new HashSet<Operation>();
		
		if(!recursive && deps!=null){
			return deps;
		}
		
		if(deps!=null){				
			for(int i=0;i<deps.size();i++){
				Operation op = deps.get(i);
				depsSet.add(op);
				depsSet.addAll(op.getDeps(true));
			}
		}
		
		return new LinkedList<Operation>(depsSet);
	}

	public void setDeps(List<Operation> deps) {
		this.deps = deps;
	}
	
	public void addADependedOperation(Operation op){
		if(deps==null) deps = new LinkedList<Operation>();
		deps.add(op);
	}
//	
//	public void removeADependedOperation(Operation op){
//		deps.remove(op);
//	}
	
	public boolean equals(Operation op){
		if(type!=op.type) return false;
		if(mergedNodePath == null && op.getMergedNodePath()!=null) return false;
		if(mergedNodePath != null && op.getMergedNodePath()==null) return false;
		if(sourceNodePath == null && op.getSourceNodePath()!=null) return false;
		if(sourceNodePath != null && op.getSourceNodePath()==null) return false;
		if(targetNodePath == null && op.getTargetNodePath()!=null) return false;
		if(targetNodePath != null && op.getTargetNodePath()==null) return false;
		if(mergedNodePath!= null && op.getMergedNodePath()!=null && !mergedNodePath.equals(op.getMergedNodePath()))return false;
		if(sourceNodePath!= null && op.getSourceNodePath()!=null && !sourceNodePath.equals(op.getSourceNodePath()))return false;
		if(targetNodePath!= null && op.getTargetNodePath()!=null && !targetNodePath.equals(op.getTargetNodePath()))return false;
		
		return true;		
	}

	public boolean isBelongTo(Operation op){
		boolean rsl = false;
		if (this.equals(op)) return rsl;
		
		switch(type){
		case COPY:
			if(op.getSourceNodePath()!=null && op.getSourceNodePath().isDescendantOf(sourceNodePath)) rsl=true;
			if(op.getTargetNodePath()!=null && op.getTargetNodePath().equals(sourceNodePath)) rsl=true;
			if(targetNodePath!=null && op.getTargetNodePath()!=null && op.getTargetNodePath().isDescendantOf(sourceNodePath)) rsl=true;
		case MOVE:
		case INSERT:
			if((op.getType()==TypeOp.INSERT) && mergedNodePath.isChildOf(op.getMergedNodePath())) rsl = true;
			break;
		case DELETE:
			if(op.getType()==TypeOp.MOVE && op.getSourceNodePath()!=null && op.getSourceNodePath().isDescendantOf(sourceNodePath)) rsl = true;
			break;	
		}	
		return rsl;
	}
	
	public String toString(){
		String str= type.toString();
		String src="";
		String mrg="";
		String trg="";
		
		if(sourceNode != null){
			src= (sourceNode.getNodeType()==Node.ELEMENT_NODE)? sourceNode.getNodeName():sourceNode.getNodeValue();
//			src=src.substring(src.lastIndexOf(":")+1);
		}
		
		if(mergedNode != null){
			mrg = (mergedNode.getNodeType()==Node.ELEMENT_NODE)? mergedNode.getNodeName():mergedNode.getNodeValue();
//			mrg=mrg.substring(mrg.lastIndexOf(":")+1);
		}
		
		if(targetNode != null){
			trg=(targetNode.getNodeType()==Node.ELEMENT_NODE)? targetNode.getNodeName():targetNode.getNodeValue();
//			trg=trg.substring(trg.lastIndexOf(":")+1);
		}
		
		switch(type){
		case INSERT:
			str+= " '" + mrg + "' at '" + trg +"'";
			break;
		case DELETE:
			str+= " '" + src + "'";
			break;
		case MOVE:
			str+= " '" + src + "' to '" + trg+"'";
			break;
		case UPDATE:
			str+= " '" + src + "'";
			break;
		default:
			str+= " '" + src + "' at '" + trg +"'";
		}
		return str;
	}
}
